% -*- coding: UTF-8 -*-
% hurlex-chapt2.tex
% hurlex 开发文档 第2章内容

\section {计算机启动过程、GRUB 以及 multiboot 标准}

\par GRUB的全称是GRand Unified Bootloader，是一个多重操作系统启动管理器，用来引导不同的操作系统。\allowbreak
Linuxer和喜欢折腾多系统的读者们想必对GRUB并不陌生吧？但是在详细描述GRUB之前，我们得先来简述一下操作系统的启动过程。\allowbreak
本着别人写过的我们就不写的原则，我推荐来自阮一峰博客的一篇文章给大家《计算机是如何启动的？》。
\footnote{地址在 http://www.ruanyifeng.com/blog/2013/02/booting.html}

\par 看过这篇文章之后，想必大家已经在宏观上对计算机的启动过程有了初步的了解。接下来我们细化这个过程，并且着重阐述\allowbreak
其中和本项目相关的内容。一直没有说明的是，我们的目标内核是32位的。因为我们只是原理学习，64位繁杂的细节会使得\allowbreak
整个项目难度加大，这是我不愿意看到的。
\footnote{其实你可以理解为我压根就不懂64位，所以不敢过多谈论。}

\par 我们先来一起复习计算机原理之类的课程中对于CPU寻址的一些概念吧。首先，我们的内核使用32位的地址总线来寻址，\allowbreak
所以能编址出2的32次方，也就是4G的地址空间。那么第一个问题是，这4G的空间指向哪里？我想大多数读者的第一反应\allowbreak
都是内存吧？我们知道在主板上除了内存还有BIOS、显卡、声卡、网卡\footnote{这里就原谅我使用这些不专业的词汇吧。}\allowbreak
等外部设备，CPU需要和这些外设进行通信。那么实现通信自然就得有地址，不然怎么表示数据的去向呢？比如显卡内部就有自己的一些存储单元\allowbreak
\footnote{甚至还有独立的GPU，我们这个简单的内核不用关注这个。}。在x86下，当需要访问这些存储单元的时候，就需要给予\allowbreak
不同的访问地址来区分每一个读写单元。

\par 说到这里，我们需要引出两个专业名词：端口统一编址和端口独立编址。还记得我们刚说的4G地址空间吗？所谓的\allowbreak
端口统一编址就是把所有和外设存储单元对应的端口直接编址在这4G的地址空间里，当我们对某一个地址进行访问的时候实际上\allowbreak
是在访问某个外设的存储单元。而端口独立编址就是说这些端口没有编址在地址空间里，而是另行独立编址。\allowbreak
而x86架构部分的采用了端口独立编址，又部分的采用了端口统一编址。部分外设的部分存储单元直接可以通过某个内存地址访问，\allowbreak
而其他部分在一个独立的端口地址空间中，需要使用in/out指令去访问，我们用到的时候再来细说。

\par 上文简单的介绍了一下地址空间的概念，接下来我们详细分析CPU在加电后的启动过程。这里可能比较枯燥和难以理解，但是没关系，\allowbreak
这里的流程是固化的，程序员们能做的很有限。\footnote{当然了，设计BIOS的程序员可以在这里大显身手。}\allowbreak
我增加这一章只是为了读者们能够充分理解我们之后内容的原理，并没有和编程相关的东西，所以大家只要大致理解就好。

\par 我们从按下电源开关开始。首先是CPU重置。主板加电之后在电压尚未稳定之前，主板上的北桥控制芯片会向CPU发出重置信号（Reset），\allowbreak
此时CPU进行初始化。当电压稳定后，控制芯片会撤销Reset信号，CPU便开始了模式化的工作。此时形成的第一条指令的地址是0xFFFFFFF0\allowbreak
\footnote{对这个地址有疑问的话，请参考《Intel IA-32 Intel Architecture Software Developer’s Manual Volume 3:System \allowbreak
Programming Guide Section》的9.1.4 章节。或者参考我博客的一篇文章《基于Intel 80×86CPU的IBM PC及其兼容计算机的启动流程》\allowbreak
，地址是：http://www.0xffffff.org/?p=310 P.S. 古老的8086处理器是0xFFFF0这个地址。}\allowbreak
，从这里开始，CPU就进入了一个"取指令-翻译指令-执行"的循环了。所以我们需要做的就是在各个阶段提供给CPU相关的数据，\allowbreak
以完成这个"接力赛"。这个接力过程中任何一个环节如果出现致命问题，其导致的直接后果就是宕机。死机是最好的结果，\allowbreak
最坏的结果是程序在"默默的"破坏我们的数据，所以一定要谨慎对待。

\par 那么，这个地址指向哪呢？大家一定想到了，它指向BIOS芯片里。我们刚刚说过，在4G的地址空间里，有一些地址是分给外设的，\allowbreak
这个地址便是映射到BIOS的。我们知道，计算机刚加电的时候内存等芯片尚未初始化，所以也只能是指向BIOS芯片里已经被"固化"的指令了。\allowbreak

\par 紧接着就是BIOS的POST（Power On Self Test，上电自检）过程了，BIOS对计算机各个部件开始初始化，如果有错误会给出报警音。\allowbreak
当BIOS完成这些工作之后，它的任务就是在外部存储设备中寻找操作系统，而我们最常用的外存自然就是硬盘了。自己安装过操作系统的\allowbreak
读者应该都设置过BIOS选项吧？BIOS里面就有一张启动设备表\footnote{其实严格的说，数据保存在CMOS芯片里，BIOS存放的是程序代码。}，\allowbreak
BIOS会按照这个表里面列出的顺序查找可启动设备。那么怎么知道该设备是否可以启动呢？规则其实很简单：如果这个存储设备的第一个扇区\allowbreak
中512个字节\footnote{不见得所有设备的扇区都是512字节，这篇文档我们只针对PC机而言，以后不再注释。}\allowbreak
的最后两个字节是0x55和0xAA，那么该存储设备就是可启动的。这是一个约定，所以BIOS会对这个列表中的设备逐一检测，只要有一个设备满足\allowbreak
要求，后续的设备将不再测试。

\par 当BIOS找到可启动的设备后，便将该设备的第一个扇区加载到内存的0x7C00地址处，并且跳转过去执行。而我们要做的事情，便是从构造\allowbreak
这个可启动的扇区开始。

\par 因为一个扇区只有512字节，放不下太多的代码，所以常规的做法便是在这里写下载入操作系统内核的代码\footnote{或者载入另一段\allowbreak
代码，这段代码负责查找和载入内核。}，这段代码就是所谓的bootloader程序。一般意义上的bootloader负责将软硬件的环境设置到一个\allowbreak
合适的状态，然后加载操作系统内核并且移交执行权限。而GRUB是一个来自GNU项目的多操作系统启动程序。它是多启动规范的实现，\allowbreak
允许用户可以在计算机内同时拥有多个操作系统，并在计算机启动时选择希望运行的操作系统。

\par 引出GRUB的原因很简单：我不准备自己实现bootloader程序。理由有二：其一，实现bootloader牵扯太多在后期才要讲述的\allowbreak
知识。与其前期简陋的实现这个bootloader，还不如就用现成的优秀实现，以后有机会自己再学着改进；第二，我想在后面把这个小\allowbreak
内核安装到物理机器上去，而读者们想必在自己的机器上已经有了多个操作系统了。这样的话如果非得实现自己的bootloader的话，\allowbreak
势必会造成和已有操作系统的不兼容。所以，我干脆决定直接使用GRUB来加载内核。以后就能让它很简单的安装在\allowbreak
物理机器上，这样的话我们能拥有一个Linux系统和自己的小内核共存的计算机了。如果你愿意的话，也可以再加上一个Windows系统。

\par 听起来很不错吧？那么问题是怎么能让GRUB加载这个小内核呢？答案是GRUB提供的multiboot规范。这份规范是描述如何\allowbreak
构造出一个能够被GRUB识别，并且按照我们定义的规则去加载的操作系统内核。目的很明确吧？具体的协议在网上很容易检索到，\allowbreak
也有不少中文版本的翻译，所以我不再详细解释这个协议。希望对这个协议陌生的读者们先去网上得到一份具体的说明，\allowbreak
仔细的阅读一遍后再阅读下一章节。在下一章节中，我们将自己动手构建出一个可以运行的"Hello, kernel OS"程序，拭目以待吧。

